///
//
// * jdbm LICENSE v1.00
//
// *
//
// * Redistribution and use of this software and associated documentation
//
// * ("Software"), with or without modification, are permitted provided
//
// * that the following conditions are met:
//
// *
//
// * 1. Redistributions of source code must retain copyright
//
// *    statements and notices.  Redistributions must also contain a
//
// *    copy of this document.
//
// *
//
// * 2. Redistributions in binary form must reproduce the
//
// *    above copyright notice, this list of conditions and the
//
// *    following disclaimer in the documentation and/or other
//
// *    materials provided with the distribution.
//
// *
//
// * 3. The name "jdbm" must not be used to endorse or promote
//
// *    products derived from this Software without prior written
//
// *    permission of Cees de Groot.  For written permission,
//
// *    please contact cg@cdegroot.com.
//
// *
//
// * 4. Products derived from this Software may not be called "jdbm"
//
// *    nor may "jdbm" appear in their names without prior written
//
// *    permission of Cees de Groot.
//
// *
//
// * 5. Due credit should be given to the jdbm Project
//
// *    (http://jdbm.sourceforge.net/).
//
// *
//
// * THIS SOFTWARE IS PROVIDED BY THE ndbm PROJECT AND CONTRIBUTORS
//
// * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
//
// * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
//
// * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
//
// * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
//
// * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
//
// * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//
// * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
//
// * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
//
// * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
//
// * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
//
// * OF THE POSSIBILITY OF SUCH DAMAGE.
//
// *
//
// * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
//
// * Contributions are Copyright (C) 2000 by their associated contributors.
//
// *
//
// 

//*************************************************************************
//**  Included in JDMB 1.0 port to SharpDBM;  11/2013 Cyrus Neah cneah@codingreal.com
//**  SharpDBM is an independent reimplementation of the JDBM 1.0 software library  
//*************************************************************************



using System;
using SharpDBM;
using SharpDBM.recman;
using SharpDBM.helper;
using SharpDBM.btree;
using NUnit.Framework;



namespace SharpDBM.Test

.btree
{



	using RecordManager = SharpDBM.RecordManager;

	using RecordManagerFactory = SharpDBM.RecordManagerFactory;

    using TestRecordFile = SharpDBM.Test.recman.TestRecordFile;

	using ByteArrayComparator = SharpDBM.helper.ByteArrayComparator;

	using StringComparator = SharpDBM.helper.StringComparator;

	using TupleBrowser = SharpDBM.helper.TupleBrowser;

	using Tuple = SharpDBM.helper.Tuple;



	using NUnit.Framework;
	using System.Threading;
	using System.Collections;
	using System.IO;












///
//
// *  This class contains all Unit tests for {@link BTree}.
//
// *
//
// *  @author <a href="mailto:boisvert@exoffice.com">Alex Boisvert</a>
//
// *  @version $Id: TestBTree.java,v 1.8 2003/09/21 15:49:02 boisvert Exp $
//
// 

	[TestFixture]
    public class TestBTree  

	{



		  internal const bool DEBUG = false;



	  // the number of threads to be started in the synchronization test

		  internal const int THREAD_NUMBER = 5;



	  // the size of the content of the maps for the synchronization

	  // test. Beware that THREAD_NUMBER * THREAD_CONTENT_COUNT < Integer.MAX_VALUE.

		  internal const int THREAD_CONTENT_SIZE = 5;//150



	  // for how long should the threads run.

		  internal const int THREAD_RUNTIME = 10 * 1000;



		protected internal object result_;



		



		[Test]
 public virtual void setUp()

		{

			TestRecordFile.deleteTestFile();

		}



		[Test]
 public virtual void tearDown()

		{

			TestRecordFile.deleteTestFile();

		}



//    *
//
//     * Overrides TestCase.run(TestResult), so the errors from threads
//
//     * started from this thread can be added to the testresult. This is
//
//     * shown in
//
//     * http://www.javaworld.com/javaworld/jw-12-2000/jw-1221-junit.html
//
//     *
//
//     * @param result the testresult
//
//     

		



//----------------------------------------------------------------------

///
//
// * Handles the exceptions from other threads, so they are not ignored
//
// * in the junit test result. This method must be called from every
//
// * thread's run() method, if any throwables were throws.
//
// *
//
// * @param t the throwable (either from an NUnit.Framework.Assert.AreEqual, NUnit.Framework.Assert.IsTrue,
//
// * NUnit.Framework.Assert.Fail, ... method, or an uncaught exception to be added to the test
//
// * result of the junit test.
//
// 




	  protected internal virtual void handleThreadException(System.Exception t)

	  {

		lock(result_)

		{

		  //if(t is NUnit.Framework.TestCaseData.Fail)

		  //  result_.addNUnit.Framework.Assert.Failure(this,(AssertionNUnit.Framework.Assert.FailedError)t);

		  //else

		  //  result_.addError(this,t);

		}

	  }





//    *
//
//     *  Basic tests
//
//     

 
//
		[Test]
 public virtual void testBasics()
		{

			RecordManager recman;

			BTree tree;
			

			sbyte[] test, test0, test1, test2, test3;

			sbyte[] value1, value2;

			sbyte[] bytes =new sbyte["test".Length * sizeof(char)];
			System.Buffer.BlockCopy("test".ToCharArray(),0,bytes,0,"test".Length);
			test = bytes;


			test0 = Conversion.convertToByteArray("test0");

			test1 = Conversion.convertToByteArray("test1");

			test2 = Conversion.convertToByteArray("test2");

			test3 = Conversion.convertToByteArray("test3");

			value1 = Conversion.convertToByteArray("value1");

			value2 = Conversion.convertToByteArray("value2");



			if (DEBUG)
			{

				Console.WriteLine("TestBTree.testBasics");

			}



			recman = RecordManagerFactory.createRecordManager("test");

			tree = BTree.createInstance(recman, new ByteArrayComparator());



			tree.insert(test1, value1, false);

			tree.insert(test2, value2, false);



			sbyte[] result;

			result = (sbyte[] ) tree.find(test0);

			if (result != null)
			{

				throw new Error("Test0 shouldn't be found");

			}



			result = (sbyte[] ) tree.find(test1);

			if (result == null || ByteArrayComparator.compareByteArray(result, value1) != 0)
			{

				throw new Error("Invalid value for test1: " + result);

			}



			result = (sbyte[] ) tree.find(test2);

			if (result == null || ByteArrayComparator.compareByteArray(result, value2) != 0)
			{

				throw new Error("Invalid value for test2: " + result);

			}



			result = (sbyte[] ) tree.find(test3);

			if (result != null)
			{

				throw new Error("Test3 shouldn't be found");

			}



			recman.close();

		}



//    *
//
//     *  Basic tests, just use the simple test possibilities of junit (cdaller)
//
//     

 
//
		[Test]
 public virtual void testBasics2()
		{

			RecordManager recman;

			BTree tree;

			sbyte[] test, test0, test1, test2, test3;

			sbyte[] value1, value2;



			test = Conversion.convertToByteArray("test");

			test0 = Conversion.convertToByteArray("test0");

			test1 = Conversion.convertToByteArray("test1");

			test2 = Conversion.convertToByteArray("test2");

			test3 = Conversion.convertToByteArray("test3");



			value1 = Conversion.convertToByteArray("value1");

			value2 = Conversion.convertToByteArray("value2");



			if (DEBUG)

				Console.WriteLine("TestBTree.testBasics2");



			recman = RecordManagerFactory.createRecordManager("test");

			tree = BTree.createInstance(recman, new ByteArrayComparator());



			tree.insert(test1, value1, false);

			tree.insert(test2, value2, false);



			NUnit.Framework.Assert.AreEqual(null, tree.find(test0));

			NUnit.Framework.Assert.AreEqual(0, ByteArrayComparator.compareByteArray(value1, (sbyte[] ) tree.find(test1)));

			NUnit.Framework.Assert.AreEqual(0, ByteArrayComparator.compareByteArray(value2, (sbyte[] ) tree.find(test2)));

			NUnit.Framework.Assert.AreEqual(null, (sbyte[] ) tree.find(test3));



			recman.close();

		 }





//    *
//
//     *  Test what happens after the recmanager has been closed but the
//
//     *  btree is accessed. WHAT SHOULD HAPPEN???????????
//
//     * (cdaller)
//
//     

 
//
		[Test]
 public virtual void testClose()

		{

			RecordManager recman;

			BTree tree;

			sbyte[] test, test0, test1, test2, test3;

			sbyte[] value1, value2;



			test = Conversion.convertToByteArray("test");

			test0 = Conversion.convertToByteArray("test0");

			test1 = Conversion.convertToByteArray("test1");

			test2 = Conversion.convertToByteArray("test2");

			test3 = Conversion.convertToByteArray("test3");



			value1 = Conversion.convertToByteArray("value1");

			value2 = Conversion.convertToByteArray("value2");



			if (DEBUG)

				Console.WriteLine("TestBTree.testClose");



			recman = RecordManagerFactory.createRecordManager("test");

			tree = BTree.createInstance(recman, new ByteArrayComparator());



			tree.insert(test1, value1, false);

			tree.insert(test2, value2, false);



			NUnit.Framework.Assert.AreEqual(null, tree.find(test0));

			NUnit.Framework.Assert.AreEqual(0, ByteArrayComparator.compareByteArray(value1, (sbyte[] ) tree.find(test1)));

			NUnit.Framework.Assert.AreEqual(0, ByteArrayComparator.compareByteArray(value2, (sbyte[] ) tree.find(test2)));

			NUnit.Framework.Assert.AreEqual(null, (sbyte[] ) tree.find(test3));



			recman.close();



			try
			{

				tree.browse();

				NUnit.Framework.Assert.Fail("Should throw an Exception on access on not opened btree");

			}
			catch(Exception except)
			{

			// ignore

			}



			try
			{

				tree.find(test0);

				NUnit.Framework.Assert.Fail("Should throw an Exception on access on not opened btree");

			}
			catch(Exception except)
			{

			// ignore

			}



			try
			{

				tree.findGreaterOrEqual(test0);

				NUnit.Framework.Assert.Fail("Should throw an Exception on access on not opened btree");

			}
			catch(Exception except)
			{

			// ignore

			}



			try
			{

				tree.insert(test2, value2, false);

				NUnit.Framework.Assert.Fail("Should throw an Exception on access on not opened btree");

			}
			catch(Exception except)
			{

			// ignore

			}



			try
			{

				tree.remove(test0);

				NUnit.Framework.Assert.Fail("Should throw an Exception on access on not opened btree");

			}
			catch(Exception except)
			{

			// ignore

			}



//        
//
//        try {
//
//            tree.size();
//
//            NUnit.Framework.Assert.Fail( "Should throw an Exception on access on not opened btree" );
//
//        } catch( Exception except ) {
//
//            // ignore
//
//        }
//
//        

		 }





//    *
//
//     *  Test to insert different objects into one btree. (cdaller)
//
//     

 
//
		[Test]
 public virtual void testInsert()

		{

			RecordManager recman;

			BTree tree;



			if (DEBUG)

				Console.WriteLine("TestBTree.testInsert");



			recman = RecordManagerFactory.createRecordManager("test");

			tree = BTree.createInstance(recman, new StringComparator());



		// insert differnt objects and retrieve them

			tree.insert("test1", "value1",false);

			tree.insert("test2","value2",false);

			tree.insert("one", (int)(1),false);

			tree.insert("two",(long)(2),false);

			tree.insert("myownobject",new TestObject((int)(234)),false);



			NUnit.Framework.Assert.AreEqual("value2",(string)tree.find("test2"));

			NUnit.Framework.Assert.AreEqual("value1",(string)tree.find("test1"));

			NUnit.Framework.Assert.AreEqual((int)(1),(int)tree.find("one"));

			NUnit.Framework.Assert.AreEqual((long)(2),(long)tree.find("two"));



		// what happens here? must not be replaced, does it return anything?

		// probably yes!

			NUnit.Framework.Assert.AreEqual("value1",tree.insert("test1","value11",false));

			NUnit.Framework.Assert.AreEqual("value1",tree.find("test1")); // still the old value?

			NUnit.Framework.Assert.AreEqual("value1",tree.insert("test1","value11",true));

			NUnit.Framework.Assert.AreEqual("value11",tree.find("test1")); // now the new value!



			TestObject expected_obj = new TestObject((int)(234));

			TestObject btree_obj = (TestObject)tree.find("myownobject");

			NUnit.Framework.Assert.AreEqual(expected_obj, btree_obj);



			recman.close();

		}





//    *
//
//     *  Test to remove  objects from the btree. (cdaller)
//
//     

 
//
		[Test]
 public virtual void testRemove()

		{

			RecordManager recman;

			BTree tree;



			if (DEBUG)
			{

				Console.WriteLine("TestBTree.testRemove");

			}



			recman = RecordManagerFactory.createRecordManager("test");

			tree = BTree.createInstance(recman, new StringComparator());



			tree.insert("test1", "value1",false);

			tree.insert("test2","value2",false);

			NUnit.Framework.Assert.AreEqual("value1",(string)tree.find("test1"));

			NUnit.Framework.Assert.AreEqual("value2",(string)tree.find("test2"));

			tree.remove("test1");

			NUnit.Framework.Assert.AreEqual(null,(string)tree.find("test1"));

			NUnit.Framework.Assert.AreEqual("value2",(string)tree.find("test2"));

			tree.remove("test2");

			NUnit.Framework.Assert.AreEqual(null,(string)tree.find("test2"));



			int iterations = 10;



			for (int count = 0; count < iterations; count++)
			{

				tree.insert("num"+count, (int)(count), false);

			}



			NUnit.Framework.Assert.AreEqual(iterations, tree.size());



			for (int count = 0; count < iterations; count++)
			{

				NUnit.Framework.Assert.AreEqual((int)(count), tree.find("num" + count));

			}



			for (int count = 0; count < iterations; count++)
			{

			   tree.remove("num" + count);

			}



			NUnit.Framework.Assert.AreEqual(0, tree.size());



			recman.close();

		}



//    *
//
//     *  Test to find differents objects in the btree. (cdaller)
//
//     

 
//
		[Test]
 public virtual void testFind()

		{

			RecordManager recman;

			BTree tree;



			if (DEBUG)

				Console.WriteLine("TestBTree.testFind");



			recman = RecordManagerFactory.createRecordManager("test");

			tree = BTree.createInstance(recman, new StringComparator());



			tree.insert("test1", "value1",false);

			tree.insert("test2","value2",false);



			object @value = tree.find("test1");

			NUnit.Framework.Assert.IsTrue(@value is string);

			NUnit.Framework.Assert.AreEqual("value1",@value);



			tree.insert("","Empty String as key",false);

			NUnit.Framework.Assert.AreEqual("Empty String as key",(string)tree.find(""));



			NUnit.Framework.Assert.AreEqual(null,(string)tree.find("someoneelse"));



			recman.close();

		}





//    *
//
//     *  Test to insert, retrieve and remove a large amount of data. (cdaller)
//
//     

 
//
		[Test]
 public virtual void testLargeDataAmount()

		{



			RecordManager recman;

			BTree tree;



			if (DEBUG)

				Console.WriteLine("TestBTree.testLargeDataAmount");



			recman = RecordManagerFactory.createRecordManager("test");

		// recman = new SharpDBM.recman.BaseRecordManager( "test" );



			tree = BTree.createInstance(recman, new StringComparator());

		// tree.setSplitPoint( 4 );



			//int iterations = 10000;
            int iterations = 3;



		// insert data

			for (int count = 0; count < iterations; count++)
			{

			   try
			   {

				NUnit.Framework.Assert.AreEqual(null,tree.insert("num"+count,(int)(count),false));

			   }
			   catch (IOException except)
			   {

				   Console.WriteLine(except.StackTrace);

				   throw except;

			   }

			}



		   // find data

			 for(int count = 0; count < iterations; count++)

			 {

			   NUnit.Framework.Assert.AreEqual((int)(count), tree.find("num"+count));

			 }



			 // delete data

			 for(int count = 0; count < iterations; count++)

			 {

			   NUnit.Framework.Assert.AreEqual((int)(count),tree.remove("num"+count));

			 }



			 NUnit.Framework.Assert.AreEqual(0,tree.size());



			 recman.close();

	   }



///
//
// * Test access from multiple threads. Assertions only work, when the
//
// * run() method is overridden and the exceptions of the threads are
//
// * added to the resultset of the TestCase. see run() and
//
// * handleException().
//
// 

 
//
	 
 [Test]
 public virtual void testMultithreadAccess()

	  {

			RecordManager recman;

			BTree tree;



			if (DEBUG)

				Console.WriteLine("TestBTree.testMultithreadAccess");



			recman = RecordManagerFactory.createRecordManager("test");

			tree = BTree.createInstance(recman, new StringComparator());



			TestThread[] thread_pool = new TestThread[THREAD_NUMBER];

			string name;

			Hashtable content;



		// create content for the tree, different content for different threads!

			for (int thread_count = 0; thread_count < THREAD_NUMBER; thread_count++)
			{

				name = "thread"+thread_count;

				content = new Hashtable();

				for(int content_count = 0; content_count < THREAD_CONTENT_SIZE; content_count++)
				{

				// guarantee, that keys and values do not overleap,

				// otherwise one thread removes some keys/values of

				// other threads!

					content.Add(name+"_"+content_count, (int)(thread_count*THREAD_CONTENT_SIZE+content_count));

				}

				thread_pool[thread_count] = new TestThread(name,tree,content);

				thread_pool[thread_count].run();

			}



			try
			{

				System.Threading.Thread.Sleep(THREAD_RUNTIME);

			}
			catch(Exception ignore)
			{

				Console.WriteLine(ignore.StackTrace);

			}



		// stop threads:

			for (int thread_count = 0; thread_count < THREAD_NUMBER; thread_count++)
			{

				if (DEBUG)
					Console.WriteLine("Stop threads");

				thread_pool[thread_count].setStop();

			}

		// wait until the threads really stop:

			try
			{

				for (int thread_count = 0; thread_count < THREAD_NUMBER; thread_count++)
				{

					if (DEBUG)
						Console.WriteLine("Join thread " + thread_count);

				//	thread_pool[thread_count].join();

					if (DEBUG)
						Console.WriteLine("Joined thread " + thread_count);

				}

				}
			catch(Exception ignore)
			{

				Console.WriteLine(ignore.StackTrace);

			}

			recman.close();

		}





//    *
//
//     *  Helper method to 'simulate' the methods of an entry set of the btree.
//
//     

 
//
		protected internal static bool containsKey(object key, BTree btree)

		{

			return (btree.find(key) != null);

		}





//    *
//
//     *  Helper method to 'simulate' the methods of an entry set of the btree.
//
//     

 
//
		protected internal static bool containsValue(object @value, BTree btree)

	  {

	// we must synchronize on the BTree while browsing

		lock (btree)
		{

			TupleBrowser browser = btree.browse();

			Tuple tuple = new Tuple();

			while(browser.getNext(tuple))
			{

			  if(tuple.Value.Equals(@value))

				return(true);

			}

		}

	//    System.out.println("Comparation of '"+value+"' with '"+ tuple.getValue()+"' NUnit.Framework.Assert.FailED");

		return(false);

	  }



//    *
//
//     *  Helper method to 'simulate' the methods of an entry set of the btree.
//
//     

 
//
		protected internal static bool contains(System.Collections.DictionaryEntry entry, BTree btree)

		{

			object tree_obj = btree.find(entry.Key);

			if (tree_obj == null)
			{

			// can't distuingish, if value is null or not found!!!!!!

				return (entry.Value == null);

			}

			return (tree_obj.Equals(entry.Value));

		}



//    *
//
//     *  Runs all tests in this class
//
//     

		static void Main(string[] args)
		{

			// junit.textui.TestRunner.run(new TestSuite(typeof(TestBTree)));

		}



//    *
//
//     * Inner class for testing puroposes only (multithreaded access)
//
//     

		internal class TestThread

		{

			internal System.Collections.Hashtable _content;

			internal BTree _btree;

			internal volatile bool _continue = true;

			internal int THREAD_SLEEP_TIME = 50; // in ms

			internal string _name;



			internal TestThread(string name, BTree btree, Hashtable content)

			{

				_content = content;

				_btree = btree;

				_name = name;

			}



			
 public virtual void setStop()

			{

				_continue = false;

			}



 
//
			private void action()
			{



				DictionaryEntry entry;

				if (DEBUG)
				{

					Console.WriteLine("Thread " + _name + ": fill btree.");

				}

				foreach (var e in _content)
				{

					entry = (DictionaryEntry)e;

					NUnit.Framework.Assert.AreEqual(null, _btree.insert(entry.Key, entry.Value, false));

				}



				// as other threads are filling the btree as well, the size

				// of the btree is unknown (but must be at least the size of

				// the content map)

				NUnit.Framework.Assert.IsTrue(_content.Count <= _btree.size());





				if (DEBUG)
				{

					Console.WriteLine("Thread " + _name + ": iterates btree.");

				}

				foreach (var e in _content)
				{
					{

						entry = (DictionaryEntry)e;

						NUnit.Framework.Assert.AreEqual(entry.Value, _btree.find(entry.Key));

						//NUnit.Framework.Assert.IsTrue(Contains(entry, _btree));

						NUnit.Framework.Assert.IsTrue(containsKey(entry.Key, _btree));

						NUnit.Framework.Assert.IsTrue(containsValue(entry.Value, _btree));

					}



					

					object key;

					if (DEBUG)
					{

						Console.WriteLine("Thread " + _name + ": removes his elements from the btree.");

					}

					foreach (DictionaryEntry v in _content)
					{

						key = v.Key;

						_btree.remove(key);

						NUnit.Framework.Assert.IsTrue(!containsKey(key, _btree));

					}

				}
			}



			
 public virtual void run()

			{

			  if(DEBUG)

				Console.WriteLine("Thread "+_name+": started.");

			  try
			  {

				while(_continue)
				{

				  action();

				  try
				  {

					System.Threading.Thread.Sleep(THREAD_SLEEP_TIME);

				  }
				  catch(Exception except)
				  {

					Console.WriteLine(except.StackTrace);

				  }

				}

				  }
			  catch(System.Exception t)
			  {

				if (DEBUG)
				{

				  Console.WriteLine("Thread "+_name+" threw an exception:");

				  Console.WriteLine(t.StackTrace);

				}

				//handleThreadException(t);

			  }

			  if(DEBUG)

				Console.WriteLine("Thread "+_name+": stopped.");

			}

		  } // end of class TestThread

	}





///
//
// * class for testing puroposes only (store as value in btree) not
//
// * implemented as inner class, as this prevents Serialization if
//
// * outer class is not Serializable.
//
// 

	[System.Serializable]
	internal class TestObject

	{



		internal object _content;



		private TestObject()

		{

		// empty

		}





		public TestObject(object content)

		{

			_content = content;

		}





		internal virtual object getContent()

		{

			return _content;

		}





		public override bool Equals(object obj)

		{

			if (! (obj is TestObject))
			{

				return false;

			}

			return _content.Equals(((TestObject) obj).getContent());

		}



		public override string ToString()

		{

			return("TestObject {content='" + _content + "'}");

		}



	} // TestObject




}